﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Reflection;

namespace Viyi.Util.Data
{
    /// <summary>
    /// 对<see cref="DataRow"/>进行扩展，以方便从<c>DataRow</c>对象获取各种类型的数据。
    /// </summary>
    public static class DataRowUtil
    {
        #region ContainsColumn
        /// <summary>
        /// 判断DataRow中是否包含指定名称的列。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnName">列名称</param>
        /// <returns>如果DataRow所在DataTable包含指定名称的列，返回true，否则返回false。</returns>
        public static bool IsContainsColumn(this DataRow row, string columnName)
        {
            return row.Table.Columns.Contains(columnName);
        }
        #endregion

        #region ObjectValue
        /// <summary>
        /// 从DataRow中取出指定列的数据，并把它当作object对象返回。
        /// 如果取出的数据为DbNull，返回指定的默认值。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnName">列名称</param>
        /// <param name="defaultValue">代替<c>DbNull</c>的默认值</param>
        /// <returns>返回取出的数据，如果是<c>DbNull</c>，则用<c>defaultValue</c>代替。</returns>
        public static object ObjectValue(this DataRow row, string columnName, object defaultValue = null)
        {
            return row.IsNull(columnName) ? defaultValue : row[columnName];
        }

        /// <summary>
        /// 从DataRow中取出指定列的数据，并把它当作object对象返回。
        /// 如果取出的数据为DbNull，返回指定的默认值。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="defaultValue">代替<c>DbNull</c>的默认值</param>
        /// <returns>返回取出的数据，如果是<c>DbNull</c>，则用<c>defaultValue</c>代替。</returns>
        public static object ObjectValue(this DataRow row, int columnIndex, object defaultValue = null)
        {
            return row.IsNull(columnIndex) ? defaultValue : row[columnIndex];
        }
        #endregion

        #region StringValue
        /// <summary>
        /// 将DataRow中指定列的数据转换为字符串值返回。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnName">列名称</param>
        /// <returns>返回从数据转换而来的<c>string</c>对象。如果源数据是<c>DbNull</c>，
        /// 则返回null</returns>
        public static string StringValue(this DataRow row, string columnName)
        {
            return row.IsNull(columnName) ? null : row[columnName].ToString();
        }

        /// <summary>
        /// 将DataRow中指定列的数据转换为字符串值返回。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnIndex">列索引</param>
        /// <returns>返回从数据转换而来的<c>string</c>对象。如果源数据是<c>DbNull</c>，
        /// 则返回null</returns>
        public static string StringValue(this DataRow row, int columnIndex)
        {
            return row.IsNull(columnIndex) ? null : row[columnIndex].ToString();
        }
        #endregion

        #region 格式化
        /// <summary>
        /// 获取由DataRow中指定列的数据按指定的格式转换得到的为字符串。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnName">列名称</param>
        /// <param name="pattern">格式模板</param>
        /// <returns>格式化结果。如果源数据是<c>DbNull</c>则返回null。</returns>
        public static string Format(this DataRow row, string columnName, string pattern)
        {
            return pattern == null
                ? StringValue(row, columnName)
                : Format(pattern, row.Field<object>(columnName));
        }

        /// <summary>
        /// 获取由DataRow中指定列的数据按指定的格式转换得到的为字符串。
        /// </summary>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="pattern">格式模板</param>
        /// <returns>格式化结果。如果源数据是<c>DbNull</c>则返回null。</returns>
        public static string Format(this DataRow row, int columnIndex, string pattern)
        {
            return pattern == null
                ? StringValue(row, columnIndex)
                : Format(pattern, row.Field<object>(columnIndex));
        }

        static string Format(string pattern, object value)
        {
            if (!pattern.Contains("{0")) { pattern = "{0:" + pattern + "}"; }
            return string.Format(pattern, value);
        }
        #endregion

        #region TemplateValue
        /// <summary>
        /// 使用Convert将DataRow指定Key的值转换为模板类型的数值，如果转换失败，返回该类型的默认值
        /// </summary>
        /// <typeparam name="T">目标类型</typeparam>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnName">列名称</param>
        /// <returns>转换后的值，若转换失败返回类型<c>default(T)</c>。</returns>
        public static T Value<T>(this DataRow row, string columnName)
        {
            return row.Value<T>(columnName, default(T));
        }

        /// <summary>
        /// 使用Convert将DataRow指定Key的值转换为模板类型的数值，如果转换失败，返回该类型的默认值
        /// </summary>
        /// <typeparam name="T">目标类型</typeparam>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnIndex">列索引</param>
        /// <returns>转换后的值，若转换失败返回类型<c>default(T)</c>。</returns>
        public static T Value<T>(this DataRow row, int columnIndex)
        {
            return row.Value<T>(columnIndex, default(T));
        }

        /// <summary>
        /// 使用Convert将DataRow指定的Key值转换为模板类型的数值，如果转换失败，返回指定的默认值
        /// </summary>
        /// <typeparam name="T">目标类型</typeparam>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnName">列名称</param>
        /// <param name="defaultValue">转换失败时用于返回的默认值</param>
        /// <returns>转换后的值，若转换失败返回类型<c>defaultValue</c>。</returns>
        public static T Value<T>(this DataRow row, string columnName, T defaultValue)
        {
            if (row.IsNull(columnName)) { return defaultValue; }
            var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
            try { return (T) Convert.ChangeType(row[columnName], type); }
            catch { return defaultValue; }
        }

        /// <summary>
        /// 使用Convert将DataRow指定的Key值转换为模板类型的数值，如果转换失败，返回指定的默认值
        /// </summary>
        /// <typeparam name="T">目标类型</typeparam>
        /// <param name="row">DataRow对象</param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="defaultValue">转换失败时用于返回的默认值</param>
        /// <returns>转换后的值，若转换失败返回类型<c>defaultValue</c>。</returns>
        public static T Value<T>(this DataRow row, int columnIndex, T defaultValue)
        {
            if (row.IsNull(columnIndex)) { return defaultValue; }
            var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
            try { return (T) Convert.ChangeType(row[columnIndex], type); }
            catch { return defaultValue; }
        }
        #endregion

        #region Convertion
        /// <summary>
        /// 将DataRow中的数据封装成一个<c>IDictionary</c>。
        /// </summary>
        /// <param name="me">DataRow对象</param>
        /// <returns>封装得到的<c>IDictionary&lt;string, object&gt;</c>对象。</returns>
        public static IDictionary<string, object> ToDictionary(this DataRow me)
        {
            if (me == null)
            {
                return null;
            }

            Dictionary<string, object> data = new Dictionary<string, object>();
            foreach (DataColumn column in me.Table.Columns)
            {
                data[column.ColumnName] = me.ObjectValue(column.ColumnName);
            }
            return data;
        }

        /// <summary>
        /// 将DataRow对象转换成一个NameValueCollection对象
        /// </summary>
        /// <param name="me">DataRow对象</param>
        /// <param name="formater">格式模板，通过列名查找，大小写不敏感；可以为<c>null</c></param>
        /// <returns>封装后的NameValueCollection</returns>
        public static NameValueCollection ToNameValueCollection(this DataRow me, NameValueCollection formater = null)
        {
            if (me == null)
            {
                return null;
            }

            NameValueCollection data = new NameValueCollection();
            foreach (DataColumn column in me.Table.Columns)
            {
                string pattern = formater == null ? null : formater[column.ColumnName];
                data[column.ColumnName] = me.Format(column.ColumnName, pattern);
            }
            return data;
        }

        /// <summary>
        /// 将 <c>DataRow</c> 对象转换为指定类型的模型对象，字段名和对象属性名会进行对应（不区分大小写）。
        /// </summary>
        /// <typeparam name="T">模型类型</typeparam>
        /// <param name="dataRow"></param>
        /// <param name="isThrowOnError">指定不能为模型属性赋值时（如类型不兼容），是否抛出异常</param>
        /// <returns></returns>
        public static T ToModel<T>(this DataRow dataRow, bool isThrowOnError = false)
            where T : new()
        {
            if (dataRow == null)
            {
                T @default = default(T);

                // 只有不能为 null 的类型且允许抛异常的时候抛异常
                if (!typeof(T).IsByRef && isThrowOnError)
                {
                    throw new ArgumentNullException(nameof(dataRow));
                }
                return @default;
            }

            T @object = new T();
            ToModel(dataRow, @object, isThrowOnError);
            return @object;
        }

        /// <summary>
        /// 将 <c>DataRow</c> 对象所包含的值赋给指定的对象，字段名和对象属性名会进行对应（不区分大小写）。
        /// </summary>
        /// <param name="dataRow"></param>
        /// <param name="object"></param>
        /// <param name="isThrowOnError">指定不能为模型属性赋值时（如类型不兼容），是否抛出异常</param>
        /// <returns></returns>
        public static object ToModel(this DataRow dataRow, object @object,
            bool isThrowOnError = false)
        {
            Type type = @object.GetType();

            PropertyInfo[] memberInfoes = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (PropertyInfo prop in memberInfoes)
            {
                // Contains 不区分大小写
                if (!prop.CanWrite || !dataRow.Table.Columns.Contains(prop.Name))
                {
                    continue;
                }

                object objValue = ObjectValue(dataRow, prop.Name);
                Type pType = prop.PropertyType;

                // 处理值为 null 或 DBNull(转换成 null) 的情况
                if (objValue == null)
                {
                    if (!pType.IsValueType || Nullable.GetUnderlyingType(pType) != null)
                    {
                        prop.SetValue(@object, null, null);
                    }

                    continue;
                }

                // 获取值类型
                Type vType = objValue.GetType();
                vType = Nullable.GetUnderlyingType(vType) ?? vType;

                // 处理可以直接赋值的情况
                if (pType.IsAssignableFrom(vType))
                {
                    prop.SetValue(@object, objValue, null);
                    continue;
                }

                object value;

                // 处理不能直接赋值的情况
                try
                {
                    // 特殊处理枚举类型
                    value = pType.IsEnum
                        ? Enum.ToObject(pType, objValue)
                        : Convert.ChangeType(objValue, pType);
                }
                catch (Exception)
                {
                    if (isThrowOnError)
                    {
                        throw;
                    }

                    // 不能转换值，忽略
                    continue;
                }

                prop.SetValue(@object, value, null);
            }

            return @object;
        }
        #endregion
    }
}
